package cn.stylefeng.guns.modular.business.service.impl;

import cn.hutool.core.util.StrUtil;
import cn.stylefeng.guns.alipay.AliPayApi;
import cn.stylefeng.guns.alipay.AliPayApiConfig;
import cn.stylefeng.guns.alipay.AliPayApiConfigKit;
import cn.stylefeng.guns.base.pojo.page.LayuiPageFactory;
import cn.stylefeng.guns.base.pojo.page.LayuiPageInfo;
import cn.stylefeng.guns.config.redis.redis.util.Redishandler0;
import cn.stylefeng.guns.core.MessageNoticeType;
import cn.stylefeng.guns.core.MoneyLogType;
import cn.stylefeng.guns.core.PayModeType;
import cn.stylefeng.guns.core.RecoverConst;
import cn.stylefeng.guns.modular.business.entity.MoneyLog;
import cn.stylefeng.guns.modular.business.entity.UserTransactionLog;
import cn.stylefeng.guns.modular.business.mapper.UserTransactionLogMapper;
import cn.stylefeng.guns.modular.business.model.dto.TransactionLogDTO;
import cn.stylefeng.guns.modular.business.model.params.MessageDTO;
import cn.stylefeng.guns.modular.business.model.params.MessageType;
import cn.stylefeng.guns.modular.business.model.params.UserTransactionLogParam;
import cn.stylefeng.guns.modular.business.model.params.WxMessageDTO;
import cn.stylefeng.guns.modular.business.model.result.UserTransactionLogResult;
import cn.stylefeng.guns.modular.business.service.MessageNoticeService;
import cn.stylefeng.guns.modular.business.service.MessageService;
import cn.stylefeng.guns.modular.business.service.MoneyLogService;
import cn.stylefeng.guns.modular.business.service.UserTransactionLogService;
import cn.stylefeng.guns.pay.entity.AliPayBean;
import cn.stylefeng.guns.pay.entity.WxPayBean;
import cn.stylefeng.guns.pay.kit.IpKit;
import cn.stylefeng.guns.sys.core.constant.CertificationStatus;
import cn.stylefeng.guns.sys.core.exception.enums.BizExceptionEnum;
import cn.stylefeng.guns.sys.core.util.DateUtil;
import cn.stylefeng.guns.sys.core.util.ODDGenerator;
import cn.stylefeng.guns.sys.core.util.StringUtils;
import cn.stylefeng.guns.sys.modular.system.entity.User;
import cn.stylefeng.guns.sys.modular.system.service.UserService;
import cn.stylefeng.guns.wxpay.WxPayApi;
import cn.stylefeng.guns.wxpay.constant.enums.SignType;
import cn.stylefeng.guns.wxpay.kit.WxPayKit;
import cn.stylefeng.guns.wxpay.model.TransfersOrderModel;
import cn.stylefeng.roses.core.reqres.response.ResponseData;
import cn.stylefeng.roses.core.util.ToolUtil;
import cn.stylefeng.roses.kernel.model.exception.ServiceException;
import com.alipay.api.domain.AlipayFundTransToaccountTransferModel;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.*;

/**
 * <p>
 * 用户交易记录 服务实现类
 * </p>
 *
 * @author pzp
 * @since 2019-08-03
 */
@Service
@Slf4j
public class UserTransactionLogServiceImpl extends ServiceImpl<UserTransactionLogMapper, UserTransactionLog> implements UserTransactionLogService {

    @Autowired
    private UserService userService;

    @Autowired
    private MoneyLogService moneyLogService;

    @Autowired
    private Redishandler0 redishandler0;

    @Value("${file.serverIP}")
    private String serverIP;

    @Autowired
    WxPayBean wxPayBean;

    @Autowired
    AliPayBean aliPayBean;

    @Autowired
    private MessageNoticeService messageNoticeService;

    @Autowired
    private MessageService messageService;

    @Override
    public void add(UserTransactionLogParam param) {
        UserTransactionLog entity = getEntity(param);
        this.save(entity);
    }

    @Override
    public void delete(UserTransactionLogParam param) {
        this.removeById(getKey(param));
    }

    @Override
    public void update(UserTransactionLogParam param) {
        UserTransactionLog oldEntity = getOldEntity(param);
        UserTransactionLog newEntity = getEntity(param);
        ToolUtil.copyProperties(newEntity, oldEntity);
        this.updateById(newEntity);
    }

    /**
     * 查询单条数据，Specification模式
     *
     * @param orderId
     * @author pzp
     * @Date 2019-08-03
     */
    @Override
    public UserTransactionLogResult findById(Integer orderId) {
        UserTransactionLog userTransactionLog = this.getById(orderId);
        UserTransactionLogResult result = new UserTransactionLogResult();
        ToolUtil.copyProperties(userTransactionLog, result);
        result.setUserName(Optional.ofNullable(userService.getById(result.getUserId())).orElse(new User()).getName());
        return result;
    }

    @Override
    public UserTransactionLogResult findBySpec(UserTransactionLogParam param) {
        return null;
    }

    @Override
    public List<UserTransactionLogResult> findListBySpec(UserTransactionLogParam param) {
        return null;
    }

    @Override
    public LayuiPageInfo findPageBySpec(String type, String payMode, String state, String beginTime, String endTime) {
        Page pageContext = getPageContext();
        IPage page = this.baseMapper.customPageList(pageContext, type, payMode, state, beginTime, endTime);
        return LayuiPageFactory.createPageInfo(page);
    }

    private Serializable getKey(UserTransactionLogParam param) {
        return param.getOrderId();
    }

    private Page getPageContext() {
        return LayuiPageFactory.defaultPage();
    }

    private UserTransactionLog getOldEntity(UserTransactionLogParam param) {
        return this.getById(getKey(param));
    }

    private UserTransactionLog getEntity(UserTransactionLogParam param) {
        UserTransactionLog entity = new UserTransactionLog();
        ToolUtil.copyProperties(param, entity);
        return entity;
    }

    /**
     * 提现记录
     *
     * @param transactionLogDTO
     * @return
     */
    @Override
    public Page<UserTransactionLogResult> record(TransactionLogDTO transactionLogDTO) {
        Page<UserTransactionLogResult> page = new Page<>(transactionLogDTO.getCurrent(), transactionLogDTO.getSize());
        return this.baseMapper.record(page, transactionLogDTO);
    }

    /**
     * 提现
     *
     * @param userId
     * @return
     */
    @Override
    public Map<String, Object> cashOut(Long userId) {
        User user = Optional.ofNullable(userService.getById(userId)).orElseThrow(() -> new ServiceException(BizExceptionEnum.NO_THIS_USER));
        if (!user.getCertificationStatus().equals(CertificationStatus.PASS.getCode())) {
            throw new ServiceException(500, "未实名，请先实名认证");
        }
        if (user.getAmount().compareTo(BigDecimal.ZERO) <= 0) {
            throw new ServiceException(500, "用户没有可用余额");
        }

        Map<String, Object> map = new HashMap<>(2);
        // 生成订单号
        String orderNo = ODDGenerator.getC(StringUtils.replace(UUID.randomUUID().toString(), "-", ""));
        map.put("orderNo", orderNo);
        //更新缓存
        redishandler0.incr(RecoverConst.USER_ORDER_REDIS_KEY, orderNo, 1);
        //5分钟就不能再发起此订单的提现
        redishandler0.expire(RecoverConst.USER_ORDER_REDIS_KEY, orderNo, 300);
        //计算用户余额
        //BigDecimal totalAmount = moneyLogService.totalAmount(userId);
        /*if (user.getAmount().compareTo(totalAmount) != 0) {
            //更新账户余额
            user.setAmount(totalAmount);
            user.setUpdateTime(new Date());
            userService.updateById(user);
        }*/
        BigDecimal totalAmount = moneyLogService.totalAmount(userId);
        if (totalAmount != null && user.getAmount().compareTo(totalAmount) != 0) {
            BigDecimal diffMoney = totalAmount.subtract(user.getAmount());
            MoneyLog moneyLog = new MoneyLog();
            moneyLog.setType(MoneyLogType.SYS_CORRECT.getCode());
            if (user.getAmount().compareTo(totalAmount) > 0) {
                moneyLog.setRemark("系统冲正，给您增加了" + diffMoney.floatValue()+ "元");
                moneyLog.setSecondType(1);
            } else {
                moneyLog.setRemark("系统冲正，减去了您" + diffMoney.floatValue()+ "元");
                moneyLog.setSecondType(2);
            }
            moneyLog.setNewAmount(user.getAmount().add(diffMoney));
            moneyLog.setOldAmount(user.getAmount());
            moneyLog.setUserId(user.getUserId());
            moneyLog.setAmount(diffMoney);
            moneyLogService.save(moneyLog);
            user.setAmount(totalAmount);
            userService.update(new UpdateWrapper<User>().setSql("amount=amount+" + diffMoney.floatValue()).eq("user_id", user.getUserId()));
            redishandler0.cacheStringValue(RecoverConst.MONEY_USER_AMOUNT, user.getUserId().toString(), user.getAmount().add(diffMoney).multiply(new BigDecimal(1000)).longValue() + "");
        }
        map.put("totalAmount", user.getAmount());
        return map;
    }

    /**
     * 确认提现
     *
     * @param userId  用户id
     * @param orderNo 订单号
     * @param money   金额
     * @param payMode 支付方式
     * @param account 账户
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void confirmCashOut(Long userId, String orderNo, BigDecimal money, Integer payMode, String account) {
        //校验订单号有效性
        if (!redishandler0.hasKey(RecoverConst.USER_ORDER_REDIS_KEY + orderNo)) {
            throw new ServiceException(BizExceptionEnum.NOT_EXISTS_ORDER_NO);
        }

        //校验用户真实性、余额是否充足
        User user = Optional.ofNullable(userService.getById(userId)).orElseThrow(() -> new ServiceException(BizExceptionEnum.NO_THIS_USER));
        if (!user.getCertificationStatus().equals(CertificationStatus.PASS.getCode())) {
            throw new ServiceException(500, "未实名，请先实名认证");
        }
        //更新缓存
        redishandler0.incr(RecoverConst.USER_ORDER_REDIS_KEY, orderNo, 1);
        String num = redishandler0.getStringValue(RecoverConst.USER_ORDER_REDIS_KEY, orderNo);
        if (!"2".equals(num)) {
            throw new ServiceException(500, "提现超时，请重新发起");
        }
        //校验金额大小
        if (5000 < money.intValue()) {
            throw new ServiceException(BizExceptionEnum.MONEY_OVERRUN);
        }
        //校验用户提现次数
        if (5 <= this.count(new QueryWrapper<UserTransactionLog>().eq("delete_flag", RecoverConst.DEL_FLAG_FALSE).eq("type", 0).ne("state", 3).eq("user_id", userId).eq("DATE_FORMAT(create_time,'%Y-%m-%d')", LocalDate.now()))) {
            throw new ServiceException(BizExceptionEnum.TIME_OVERRUN);
        }
        //校验微信提现金额
        if (payMode.equals(PayModeType.WECHAT.getCode())) {
            log.info("微信提现");
            BigDecimal amountWeChat = this.baseMapper.amountByWechat(userId, LocalDate.now().toString());
            if (null != amountWeChat && amountWeChat.intValue() >= 5000) {
                throw new ServiceException(BizExceptionEnum.MONEY_OVERRUN);
            }
        } else if (payMode.equals(PayModeType.ALIPAY.getCode())) {
            log.info("支付宝提现");
        } else {
            log.info("银联提现");
        }
        //校验用户真实性、余额是否充足
        if (money.compareTo(user.getAmount()) == 1) {
            throw new ServiceException(BizExceptionEnum.NOT_SUFFICIENT_FUNDS);
        }
        //记录交易流水
        UserTransactionLog userTransactionLog = new UserTransactionLog();
        userTransactionLog.setOrderNo(orderNo);
        userTransactionLog.setUserId(userId);
        userTransactionLog.setPayMode(payMode);
        userTransactionLog.setAccount(account);
        userTransactionLog.setAmount(money);
        //直接是成功
        userTransactionLog.setState(2);
        userTransactionLog.setType(0);
        this.save(userTransactionLog);
        log.info("提现开始扣除数据库用户余额");
        //账户扣钱
        userService.update(new UpdateWrapper<User>().setSql("amount=amount-" + money).eq("user_id", userId));
        Long oldAmount = redishandler0.incr(RecoverConst.MONEY_USER_AMOUNT, userId.toString(), 0);
        BigDecimal oldM = new BigDecimal("" + oldAmount).divide(new BigDecimal(1000));
        //记录金额流水
        MoneyLog moneyLog = new MoneyLog();
        moneyLog.setUserId(userId);
        moneyLog.setOldAmount(oldM);
        moneyLog.setNewAmount(oldM.subtract(money));
        moneyLog.setComeUserId(userId);
        moneyLog.setType(MoneyLogType.CARD_CASH.getCode());
        moneyLog.setSecondType(payMode);
        moneyLog.setAmount(money.negate());
        moneyLog.setBusinessId(userTransactionLog.getOrderId());
        moneyLog.setBusinessNO(userTransactionLog.getOrderNo());
        moneyLog.setRemark("您申请的" + money.doubleValue() +"元提现已经成功到账，请注意查收");
        moneyLogService.save(moneyLog);

        //TODO 调用支付
        if(payMode.equals(PayModeType.WECHAT.getCode())) {

            Map<String, String> params = TransfersOrderModel
                    .builder()
                    .mch_appid(wxPayBean.getAppId())
                    .mchid(wxPayBean.getMchId())
                    .nonce_str(WxPayKit.generateStr())
                    .partner_trade_no(orderNo)
                    .openid(user.getOpenid())
                    .re_user_name(user.getRealName())
                    .check_name("FORCE_CHECK")
                    .amount(String.valueOf(money.multiply(new BigDecimal(100)).longValue()))
                    .desc("胖蚂蚁回收提现")
                    .spbill_create_ip(serverIP)
                    .build()
                    .creatSign(wxPayBean.getPartnerKey(), SignType.MD5);
            String xmlResult = WxPayApi.transfers(params, wxPayBean.getCertPath(), wxPayBean.getMchId());
            if(StringUtils.isEmpty(xmlResult))  {
                throw new ServiceException(500, "提现错误，请联系管理员");
            }
            log.info(xmlResult);
            Map<String, String> resultMap = WxPayKit.xmlToMap(xmlResult);
            String return_code = resultMap.get("return_code");
            String return_msg = resultMap.get("return_msg");
            if (return_code.equals("FAIL")) {
                log.error("微信提现结果为fail，请联系管理员查看问题");
                throw new ServiceException(500, "微信提现结果为fail，请联系管理员查看问题");
            }
            String result_code = resultMap.get("result_code");
            if (return_code.equals("SUCCESS") && result_code.equals("SUCCESS")) {
                log.info("微信提现结果为SUCCESS成功");
            } else {
                if (resultMap.get("err_code").equals("SYSTEMERROR")) {
                    //如果返回系统异常需要发送查询请求再次确认
                    String tResult = WxPayApi.getTransferInfo(params, wxPayBean.getCertPath(), wxPayBean.getMchId());
                    log.error(tResult);
                    throw new ServiceException(500, "微信提现出现未知错误，请联系管理员查看问题");
                } else {
                    log.error("微信提现出现未知错误，请联系管理员查看问题");
                    throw new ServiceException(500, "微信提现出现未知错误，请联系管理员查看问题");
                }
            }
        } else if (payMode.equals(PayModeType.ALIPAY.getCode())) {
            AliPayApiConfig aliPayApiConfig = AliPayApiConfig.New()
                    .setAppId(aliPayBean.getAppId())
                    .setAliPayPublicKey(aliPayBean.getPublicKey())
                    .setCharset("UTF-8")
                    .setPrivateKey(aliPayBean.getPrivateKey())
                    .setServiceUrl(aliPayBean.getServerUrl())
                    .setSignType("RSA2")
                    .build();
            AliPayApiConfigKit.setThreadLocalAliPayApiConfig(aliPayApiConfig);
            boolean isSuccess = false;
            String total_amount = money.toString();
            AlipayFundTransToaccountTransferModel model = new AlipayFundTransToaccountTransferModel();
            model.setOutBizNo(orderNo);
            model.setPayeeType("ALIPAY_LOGONID");
            model.setPayeeAccount(account);
            model.setAmount(total_amount);
            //model.setPayerShowName("胖蚂蚁回收提现");
            model.setPayeeRealName(user.getRealName());
            model.setRemark("胖蚂蚁回收提现");
            try {
                isSuccess = AliPayApi.transfer(model);
                if (!isSuccess) {
                    throw new ServiceException(500, "支付宝提现失败，请联系客服");
                } else {
                    messageNoticeService.add("提现成功通知", "您申请的" + money.toString() + "元支付宝提现已成功到账，请注意查收", MessageNoticeType.system.getCode(), userId);
                    sendSuccessMessageToUser(user.getOpenid(), money.toString(), "支付宝");
                }
            } catch (Exception e) {
                e.printStackTrace();
                throw new ServiceException(500, "支付宝提现失败，请联系客服");
            }
        } else {

        }
        log.info("提现完成，准备扣除缓存中的用户余额");
        redishandler0.incr(RecoverConst.MONEY_USER_AMOUNT, userId.toString(), money.multiply(new BigDecimal(1000)).negate().longValue());
        //订单号减1
        //redishandler0.incr(RecoverConst.USER_ORDER_REDIS_KEY,orderNo,-1);
    }

    /**
     * 发送提现通知
     * @param openId
     */
    private void sendSuccessMessageToUser(String openId, String amount, String typeStr) {
        MessageDTO messageDTO = new MessageDTO();
        messageDTO.setUserIdList(Collections.singletonList(openId));
        messageDTO.setMessageType(MessageType.TIXIAN);
        WxMessageDTO wxMessageDTO = new WxMessageDTO();
        wxMessageDTO.setFirst("您好，您申请的" + typeStr + "提现已成功到账");
        wxMessageDTO.setKeyword1(DateUtil.getDateTime(new Date()));
        wxMessageDTO.setKeyword2(typeStr);
        wxMessageDTO.setKeyword3(amount);
        wxMessageDTO.setRemark("感谢你的使用和支持！");
        messageDTO.setData(wxMessageDTO);
        messageService.sendTemplateMessage(messageDTO);
    }
}
